package org.xx.armory.service.security.impl;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xx.armory.service.ServiceContext;
import org.xx.armory.service.TempFileObject;
import org.xx.armory.service.security.CaptchaImageProvider;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Properties;
import java.util.Random;

import static javax.imageio.ImageIO.write;
import static org.xx.armory.commons.Validators.greaterThan;
import static org.xx.armory.commons.Validators.notBlank;
import static org.xx.armory.commons.Validators.notNull;

/**
 * @author Haart
 */
public final class KaptchaImageProvider
        implements CaptchaImageProvider {
    private static final int DEFAULT_CODE_LENGTH = 6;
    private static final int DEFAULT_IMAGE_WIDTH = 108;
    private static final int DEFAULT_IMAGE_HEIGHT = 32;
    /**
     * <strong>不使用下列字符，因为它们变形后不易辨认 'l', 'L', 'O', 'o', '0' etc... </strong>
     */
    private static final char[] CODE_SET = {'a', 'c', 'd', 'f', 'g', 'h',
            'k', 'p', 'x', 'y', '2', '3', '5', '6', '7', '8'};
    private final Logger logger = LoggerFactory.getLogger(KaptchaImageProvider.class);

    /**
     *
     */
    public KaptchaImageProvider() {
    }

    /**
     * 创建一个缺省的配置。
     *
     * @return 缺省的配置。
     */
    @SuppressWarnings("SpellCheckingInspection")
    private static Properties createDefaultConfig() {
        final Properties props = new Properties();
        props.setProperty("kaptcha.border", "no");
        props.setProperty("kaptcha.textproducer.font.names", "Arial");
        props.setProperty("kaptcha.textproducer.font.size", "24");
        props.setProperty("kaptcha.textproducer.char.space", "1");
        // props.setProperty("kaptcha.obscurificator.impl",
        // "com.google.code.kaptcha.impl.ShadowGimpy");
        props.setProperty("kaptcha.obscurificator.impl", SimpleGimpy.class.getName());
        props.setProperty("kaptcha.noise.impl", SimpleNoise.class.getName());
        return props;
    }

    /**
     * 生成随机码。
     *
     * @param codeLength
     *         随机码的长度。
     * @return 生成的随机码, 只包含预设的字符。
     * @throws IllegalArgumentException
     *         如果参数{@code codeLength}小于或者等于{@code 0}。
     */
    public static String randomCode(int codeLength) {
        greaterThan(codeLength, "codeLength", 0);

        final StringBuilder sb = new StringBuilder();
        final Random rnd = new Random();

        for (int i = 0; i < codeLength; ++i) {
            sb.append(CODE_SET[rnd.nextInt(CODE_SET.length)]);
        }

        return sb.toString();
    }

    /**
     * 生成验证码图片。
     *
     * @param captchaCode
     *         随机验证码。
     * @param width
     *         图片宽度(单位是像素)。
     * @param height
     *         图片高度(单位是像素)。
     * @return 表示图片内容的字节数组。
     * @throws IOException
     *         如果在内存中绘制图像时出现错误，或者将图像内容转化为字节数组时出现错误。
     * @throws NullPointerException
     *         如果参数{@code captchaCode}是{@code null}。
     * @throws IllegalArgumentException
     *         如果参数{@code captchaCode}只包含空白字符, 或者参数{@code width}小于等于{@code 10}, 或者参数{@code height}
     *         小于等于{@code 10}。
     */
    public static byte[] drawImage(
            String captchaCode,
            int width,
            int height
    )
            throws IOException {
        notBlank(captchaCode, "captchaCode");
        greaterThan(width, "width", 10);
        greaterThan(height, "height", 10);

        final com.google.code.kaptcha.Producer producer = new com.google.code.kaptcha.impl.DefaultKaptcha();
        final Properties props = new Properties(ConfigHolder.INSTANCE);
        props.setProperty("kaptcha.image.width", String.valueOf(width));
        props.setProperty("kaptcha.image.height", String.valueOf(height));
        ((com.google.code.kaptcha.util.Configurable) producer).setConfig(
                new com.google.code.kaptcha.util.Config(props));

        try (final ByteArrayOutputStream output = new ByteArrayOutputStream()) {
            write(producer.createImage(captchaCode), "PNG", output);
            return output.toByteArray();
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final TempFileObject execute(ServiceContext ctx) {
        notNull(ctx, "ctx");

        // 生成随机验证码。
        final String captchaCode = randomCode(DEFAULT_CODE_LENGTH);

        // 将验证码放进令牌。
        ctx.getToken().putString(CAPTCHA_KEY, captchaCode);

        // 生成图片。
        try {
            return new TempFileObject("captcha-image.png", drawImage(captchaCode, DEFAULT_IMAGE_WIDTH, DEFAULT_IMAGE_HEIGHT));
        } catch (IOException ex) {
            logger.error("cannot draw captcha image", ex);
            return TempFileObject.NONE;
        }
    }

    /**
     * @author Haart
     */
    private static class ConfigHolder {
        /**
         * 缺省配置实例
         */
        static final Properties INSTANCE = createDefaultConfig();
    }
}
